package org.apache.maven.project;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginContainer;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.Repository;
import org.codehaus.plexus.util.xml.Xpp3Dom;

/** @deprecated */
@Deprecated
public final class ModelUtils {

  /**
   * This should be the resulting ordering of plugins after merging:
   * <p/>
   * Given:
   * <p/>
   * parent: X -> A -> B -> D -> E child: Y -> A -> C -> D -> F
   * <p/>
   * Result:
   * <p/>
   * X -> Y -> A -> B -> C -> D -> E -> F
   */
  public static void mergePluginLists(PluginContainer childContainer, PluginContainer parentContainer,
      boolean handleAsInheritance) {
    if ((childContainer == null) || (parentContainer == null)) {
      // nothing to do.
      return;
    }

    List<Plugin> parentPlugins = parentContainer.getPlugins();

    if ((parentPlugins != null) && !parentPlugins.isEmpty()) {
      parentPlugins = new ArrayList<Plugin>(parentPlugins);

      // If we're processing this merge as an inheritance, we have to build up a list of
      // plugins that were considered for inheritance.
      if (handleAsInheritance) {
        for (Iterator<Plugin> it = parentPlugins.iterator(); it.hasNext();) {
          Plugin plugin = it.next();

          String inherited = plugin.getInherited();

          if ((inherited != null) && !Boolean.valueOf(inherited)) {
            it.remove();
          }
        }
      }

      List<Plugin> assembledPlugins = new ArrayList<Plugin>();

      Map<String, Plugin> childPlugins = childContainer.getPluginsAsMap();

      for (Plugin parentPlugin : parentPlugins) {
        String parentInherited = parentPlugin.getInherited();

        // only merge plugin definition from the parent if at least one
        // of these is true:
        // 1. we're not processing the plugins in an inheritance-based merge
        // 2. the parent's <inherited/> flag is not set
        // 3. the parent's <inherited/> flag is set to true
        if (!handleAsInheritance || (parentInherited == null) || Boolean.valueOf(parentInherited)) {
          Plugin childPlugin = (Plugin) childPlugins.get(parentPlugin.getKey());

          if ((childPlugin != null) && !assembledPlugins.contains(childPlugin)) {
            Plugin assembledPlugin = childPlugin;

            mergePluginDefinitions(childPlugin, parentPlugin, handleAsInheritance);

            // fix for MNG-2221 (assembly cache was not being populated for later reference):
            assembledPlugins.add(assembledPlugin);
          }

          // if we're processing this as an inheritance-based merge, and
          // the parent's <inherited/> flag is not set, then we need to
          // clear the inherited flag in the merge result.
          if (handleAsInheritance && (parentInherited == null)) {
            parentPlugin.unsetInheritanceApplied();
          }
        }

        // very important to use the parentPlugins List, rather than parentContainer.getPlugins()
        // since this list is a local one, and may have been modified during processing.
        List<Plugin> results = ModelUtils.orderAfterMerge(assembledPlugins, parentPlugins, childContainer.getPlugins());

        childContainer.setPlugins(results);

        childContainer.flushPluginMap();
      }
    }
  }

  public static List<Plugin> orderAfterMerge(List<Plugin> merged, List<Plugin> highPrioritySource,
      List<Plugin> lowPrioritySource) {
    List<Plugin> results = new ArrayList<Plugin>();

    if (!merged.isEmpty()) {
      results.addAll(merged);
    }

    List<Plugin> missingFromResults = new ArrayList<Plugin>();

    List<List<Plugin>> sources = new ArrayList<List<Plugin>>();

    sources.add(highPrioritySource);
    sources.add(lowPrioritySource);

    for (List<Plugin> source : sources) {
      for (Plugin item : source) {
        if (results.contains(item)) {
          if (!missingFromResults.isEmpty()) {
            int idx = results.indexOf(item);

            if (idx < 0) {
              idx = 0;
            }

            results.addAll(idx, missingFromResults);

            missingFromResults.clear();
          }
        } else {
          missingFromResults.add(item);
        }
      }

      if (!missingFromResults.isEmpty()) {
        results.addAll(missingFromResults);

        missingFromResults.clear();
      }
    }

    return results;
  }

  public static void mergePluginDefinitions(Plugin child, Plugin parent, boolean handleAsInheritance) {
    if ((child == null) || (parent == null)) {
      // nothing to do.
      return;
    }

    if (parent.isExtensions()) {
      child.setExtensions(true);
    }

    if ((child.getVersion() == null) && (parent.getVersion() != null)) {
      child.setVersion(parent.getVersion());
    }

    Xpp3Dom childConfiguration = (Xpp3Dom) child.getConfiguration();
    Xpp3Dom parentConfiguration = (Xpp3Dom) parent.getConfiguration();

    childConfiguration = Xpp3Dom.mergeXpp3Dom(childConfiguration, parentConfiguration);

    child.setConfiguration(childConfiguration);

    child.setDependencies(mergeDependencyList(child.getDependencies(), parent.getDependencies()));

    // from here to the end of the method is dealing with merging of the <executions/> section.
    String parentInherited = parent.getInherited();

    boolean parentIsInherited = (parentInherited == null) || Boolean.valueOf(parentInherited);

    List<PluginExecution> parentExecutions = parent.getExecutions();

    if ((parentExecutions != null) && !parentExecutions.isEmpty()) {
      List<PluginExecution> mergedExecutions = new ArrayList<PluginExecution>();

      Map<String, PluginExecution> assembledExecutions = new TreeMap<String, PluginExecution>();

      Map<String, PluginExecution> childExecutions = child.getExecutionsAsMap();

      for (PluginExecution parentExecution : parentExecutions) {
        String inherited = parentExecution.getInherited();

        boolean parentExecInherited = parentIsInherited && ((inherited == null) || Boolean.valueOf(inherited));

        if (!handleAsInheritance || parentExecInherited) {
          PluginExecution assembled = parentExecution;

          PluginExecution childExecution = childExecutions.get(parentExecution.getId());

          if (childExecution != null) {
            mergePluginExecutionDefinitions(childExecution, parentExecution);

            assembled = childExecution;
          } else if (handleAsInheritance && (parentInherited == null)) {
            parentExecution.unsetInheritanceApplied();
          }

          assembledExecutions.put(assembled.getId(), assembled);
          mergedExecutions.add(assembled);
        }
      }

      for (PluginExecution childExecution : child.getExecutions()) {
        if (!assembledExecutions.containsKey(childExecution.getId())) {
          mergedExecutions.add(childExecution);
        }
      }

      child.setExecutions(mergedExecutions);

      child.flushExecutionMap();
    }

  }

  private static void mergePluginExecutionDefinitions(PluginExecution child, PluginExecution parent) {
    if (child.getPhase() == null) {
      child.setPhase(parent.getPhase());
    }

    List<String> parentGoals = parent.getGoals();
    List<String> childGoals = child.getGoals();

    List<String> goals = new ArrayList<String>();

    if ((childGoals != null) && !childGoals.isEmpty()) {
      goals.addAll(childGoals);
    }

    if (parentGoals != null) {
      for (String goal : parentGoals) {
        if (!goals.contains(goal)) {
          goals.add(goal);
        }
      }
    }

    child.setGoals(goals);

    Xpp3Dom childConfiguration = (Xpp3Dom) child.getConfiguration();
    Xpp3Dom parentConfiguration = (Xpp3Dom) parent.getConfiguration();

    childConfiguration = Xpp3Dom.mergeXpp3Dom(childConfiguration, parentConfiguration);

    child.setConfiguration(childConfiguration);
  }

  public static List<Repository> mergeRepositoryLists(List<Repository> dominant, List<Repository> recessive) {
    List<Repository> repositories = new ArrayList<Repository>();

    for (Repository repository : dominant) {
      repositories.add(repository);
    }

    for (Repository repository : recessive) {
      if (!repositories.contains(repository)) {
        repositories.add(repository);
      }
    }

    return repositories;
  }

  public static void mergeFilterLists(List<String> childFilters, List<String> parentFilters) {
    for (String f : parentFilters) {
      if (!childFilters.contains(f)) {
        childFilters.add(f);
      }
    }
  }

  private static List<Dependency> mergeDependencyList(List<Dependency> child, List<Dependency> parent) {
    Map<String, Dependency> depsMap = new LinkedHashMap<String, Dependency>();

    if (parent != null) {
      for (Dependency dependency : parent) {
        depsMap.put(dependency.getManagementKey(), dependency);
      }
    }

    if (child != null) {
      for (Dependency dependency : child) {
        depsMap.put(dependency.getManagementKey(), dependency);
      }
    }

    return new ArrayList<Dependency>(depsMap.values());
  }

}
